<template>
  <view
    class="lime-echart"
    :style="[customStyle]"
    v-if="canvasId"
    ref="limeEchart"
    :aria-label="ariaLabel"
  >
    <!-- #ifndef APP-NVUE -->
    <canvas
      class="lime-echart__canvas"
      v-if="use2dCanvas"
      type="2d"
      :id="canvasId"
      :style="canvasStyle"
      :disable-scroll="isDisableScroll"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    />
    <canvas
      class="lime-echart__canvas"
      v-else
      :width="nodeWidth"
      :height="nodeHeight"
      :style="canvasStyle"
      :canvas-id="canvasId"
      :id="canvasId"
      :disable-scroll="isDisableScroll"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    />
    <view
      class="lime-echart__mask"
      v-if="isPC"
      @mousedown="touchStart"
      @mousemove="touchMove"
      @mouseup="touchEnd"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    >
    </view>
    <canvas
      v-if="isOffscreenCanvas"
      :style="offscreenStyle"
      :canvas-id="offscreenCanvasId"
    ></canvas>
    <!-- #endif -->
    <!-- #ifdef APP-NVUE -->
    <web-view
      class="lime-echart__canvas"
      :id="canvasId"
      :style="canvasStyle"
      :webview-styles="webviewStyles"
      ref="webview"
      src="/uni_modules/lime-echart/static/uvue.html?v=1"
      @pagefinish="finished = true"
      @onPostMessage="onMessage"
    ></web-view>
    <!-- #endif -->
  </view>
</template>

<script>
// @ts-nocheck
// #ifndef APP-NVUE
import { Canvas, setCanvasCreator, dispatch } from "./canvas";
import {
  wrapTouch,
  convertTouchesToArray,
  devicePixelRatio,
  sleep,
  canIUseCanvas2d,
  getRect,
  getDeviceInfo,
} from "./utils";
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from "./utils";
import { Echarts } from "./nvue";
// #endif

/**
 * LimeChart 图表
 * @description 全端兼容的eCharts
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4899

 * @property {String} customStyle 自定义样式
 * @property {String} type 指定 canvas 类型
 * @value 2d 使用canvas 2d，部分小程序支持
 * @value '' 使用原生canvas，会有层级问题
 * @value bottom right	不缩放图片，只显示图片的右下边区域
 * @property {Boolean} isDisableScroll	 
 * @property {number} beforeDelay = [30]  延迟初始化 (毫秒)
 * @property {Boolean} enableHover PC端使用鼠标悬浮

 * @event {Function} finished 加载完成触发
 */
export default {
  name: "lime-echart",
  props: {
    // #ifdef MP-WEIXIN || MP-TOUTIAO
    type: {
      type: String,
      default: "2d",
    },
    // #endif
    // #ifdef APP-NVUE
    webviewStyles: Object,
    // hybrid: Boolean,
    // #endif
    customStyle: String,
    isDisableScroll: Boolean,
    isClickable: {
      type: Boolean,
      default: true,
    },
    enableHover: Boolean,
    beforeDelay: {
      type: Number,
      default: 30,
    },
    landscape: Boolean,
  },
  data() {
    return {
      // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
      use2dCanvas: true,
      // #endif
      // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
      use2dCanvas: false,
      // #endif
      ariaLabel: "图表",
      width: null,
      height: null,
      nodeWidth: null,
      nodeHeight: null,
      // canvasNode: null,
      config: {},
      inited: false,
      finished: false,
      file: "",
      platform: "",
      isPC: false,
      isDown: false,
      isOffscreenCanvas: false,
      offscreenWidth: 0,
      offscreenHeight: 0,
    };
  },
  computed: {
    rootStyle() {
      if (this.landscape) {
        return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`;
      }
    },
    canvasId() {
      return `lime-echart${(this._ && this._.uid) || this._uid}`;
    },
    offscreenCanvasId() {
      return `${this.canvasId}_offscreen`;
    },
    offscreenStyle() {
      return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`;
    },
    canvasStyle() {
      return (
        this.rootStyle +
        (this.width && this.height
          ? "width:" + this.width + "px;height:" + this.height + "px"
          : "")
      );
    },
  },
  // #ifndef VUE3
  beforeDestroy() {
    this.clear();
    this.dispose();
    // #ifdef H5
    if (this.isPC) {
      document.removeEventListener("mousewheel", this.mousewheel);
    }
    // #endif
  },
  // #endif
  // #ifdef VUE3
  beforeUnmount() {
    this.clear();
    this.dispose();
    // #ifdef H5
    if (this.isPC) {
      document.removeEventListener("mousewheel", this.mousewheel);
    }
    // #endif
  },
  // #endif
  created() {
    // #ifdef H5
    if (!("ontouchstart" in window)) {
      this.isPC = true;
      document.addEventListener("mousewheel", this.mousewheel);
    }
    // #endif
    // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
    const { platform } = getDeviceInfo();
    this.isPC = /windows/i.test(platform);
    // #endif
    this.use2dCanvas = this.type === "2d" && canIUseCanvas2d();
  },
  mounted() {
    this.$nextTick(() => {
      this.$emit("finished");
    });
  },
  methods: {
    // #ifdef APP-NVUE
    onMessage(e) {
      const detail = e?.detail?.data[0] || null;
      const data = detail?.data;
      const key = detail?.event;
      const options = data?.options;
      const event = data?.event;
      const file = detail?.file;
      if (key == "log" && data) {
        console.log(data);
      }
      if (event) {
        this.chart.dispatchAction(event.replace(/"/g, ""), options);
      }
      if (file) {
        thie.file = file;
      }
    },
    // #endif
    setChart(callback) {
      if (!this.chart) {
        console.warn(`组件还未初始化，请先使用 init`);
        return;
      }
      if (typeof callback === "function" && this.chart) {
        callback(this.chart);
      }
      // #ifdef APP-NVUE
      if (typeof callback === "function") {
        this.$refs.webview.evalJs(
          `setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`,
        );
      }
      // #endif
    },
    setOption() {
      if (!this.chart || !this.chart.setOption) {
        console.warn(`组件还未初始化，请先使用 init`);
        return;
      }
      this.chart.setOption(...arguments);
    },
    showLoading() {
      if (this.chart) {
        this.chart.showLoading(...arguments);
      }
    },
    hideLoading() {
      if (this.chart) {
        this.chart.hideLoading();
      }
    },
    clear() {
      if (this.chart && !this.chart.isDisposed()) {
        this.chart.clear();
      }
    },
    dispose() {
      if (this.chart && !this.chart.isDisposed()) {
        this.chart.dispose();
      }
    },
    resize(size) {
      if (size && size.width && size.height) {
        this.height = size.height;
        this.width = size.width;
        if (this.chart) {
          this.chart.resize(size);
        }
      } else {
        this.$nextTick(() => {
          getRect(".lime-echart", this).then((res) => {
            if (res) {
              let { width, height } = res;
              this.width = width = width || 300;
              this.height = height = height || 300;
              this.chart.resize({ width, height });
            }
          });
        });
      }
    },
    canvasToTempFilePath(args = {}) {
      // #ifndef APP-NVUE
      const { use2dCanvas, canvasId } = this;
      return new Promise((resolve, reject) => {
        const copyArgs = Object.assign(
          {
            canvasId,
            success: resolve,
            fail: reject,
          },
          args,
        );
        if (use2dCanvas) {
          delete copyArgs.canvasId;
          copyArgs.canvas = this.canvasNode;
        }
        uni.canvasToTempFilePath(copyArgs, this);
      });
      // #endif
      // #ifdef APP-NVUE
      this.file = "";
      this.$refs.webview.evalJs(`canvasToTempFilePath()`);
      return new Promise((resolve, reject) => {
        this.$watch("file", async (file) => {
          if (file) {
            const tempFilePath = await base64ToPath(file);
            resolve(args.success({ tempFilePath }));
          } else {
            reject(args.fail({ error: `` }));
          }
        });
      });
      // #endif
    },
    async init(echarts, ...args) {
      // #ifndef APP-NVUE
      if (args && args.length == 0 && !echarts) {
        console.error(
          "缺少参数：init(echarts, theme?:string, opts?: object, callback?: function)",
        );
        return;
      }
      // #endif
      let theme = null,
        opts = {},
        callback;
      // Array.from(arguments)
      args.forEach((item) => {
        if (typeof item === "function") {
          callback = item;
        }
        if (["string"].includes(typeof item)) {
          theme = item;
        }
        if (typeof item === "object") {
          opts = item;
        }
      });
      if (this.beforeDelay) {
        await sleep(this.beforeDelay);
      }
      let config = await this.getContext();
      // #ifndef APP-NVUE
      setCanvasCreator(echarts, config);
      try {
        this.chart = echarts.init(
          config.canvas,
          theme,
          Object.assign({}, config, opts || {}),
        );

        callback?.(this.chart);
        return this.chart;
      } catch (e) {
        console.error("【lime-echarts】:", e);
        return null;
      }
      // #endif
      // #ifdef APP-NVUE
      this.chart = new Echarts(this.$refs.webview);
      this.$refs.webview.evalJs(
        `init(null, null, ${JSON.stringify(opts)}, ${theme})`,
      );
      callback?.(this.chart);
      return this.chart;
      // #endif
    },
    getContext() {
      // #ifdef APP-NVUE
      if (this.finished) {
        return Promise.resolve(this.finished);
      }
      return new Promise((resolve) => {
        this.$watch("finished", (val) => {
          if (val) {
            resolve(this.finished);
          }
        });
      });
      // #endif
      // #ifndef APP-NVUE
      return getRect(`#${this.canvasId}`, this, this.use2dCanvas).then(
        (res) => {
          if (res) {
            let dpr = devicePixelRatio;
            let { width, height, node } = res;
            let canvas;
            this.width = width = width || 300;
            this.height = height = height || 300;
            if (node) {
              const ctx = node.getContext("2d");
              canvas = new Canvas(ctx, this, true, node);
              this.canvasNode = node;
            } else {
              // #ifdef MP-TOUTIAO
              dpr = !this.isPC ? devicePixelRatio : 1; // 1.25
              // #endif
              // #ifndef MP-ALIPAY || MP-TOUTIAO
              dpr = this.isPC ? devicePixelRatio : 1;
              // #endif
              // #ifdef MP-ALIPAY || MP-LARK
              dpr = devicePixelRatio;
              // #endif
              // #ifdef WEB
              dpr = 1;
              // #endif
              this.rect = res;
              this.nodeWidth = width * dpr;
              this.nodeHeight = height * dpr;
              const ctx = uni.createCanvasContext(this.canvasId, this);
              canvas = new Canvas(ctx, this, false);
            }

            return { canvas, width, height, devicePixelRatio: dpr, node };
          } else {
            return {};
          }
        },
      );
      // #endif
    },
    // #ifndef APP-NVUE
    getRelative(e, touches) {
      let { clientX, clientY } = e;
      if (!(clientX && clientY) && touches && touches[0]) {
        clientX = touches[0].clientX;
        clientY = touches[0].clientY;
      }
      return {
        x: clientX - this.rect.left,
        y: clientY - this.rect.top,
        wheelDelta: e.wheelDelta || 0,
      };
    },
    getTouch(e, touches) {
      const { x } = (touches && touches[0]) || {};
      const touch = x ? touches[0] : this.getRelative(e, touches);
      if (this.landscape) {
        [touch.x, touch.y] = [touch.y, this.height - touch.x];
      }
      return touch;
    },
    touchStart(e) {
      this.isDown = true;
      const next = () => {
        const touches = convertTouchesToArray(e.touches);
        if (this.chart) {
          const touch = this.getTouch(e, touches);
          this.startX = touch.x;
          this.startY = touch.y;
          this.startT = new Date();
          const handler = this.chart.getZr().handler;
          dispatch.call(handler, "mousedown", touch);
          dispatch.call(handler, "mousemove", touch);
          handler.processGesture(wrapTouch(e), "start");
          clearTimeout(this.endTimer);
        }
      };
      if (this.isPC) {
        getRect(`#${this.canvasId}`, { context: this }).then((res) => {
          this.rect = res;
          next();
        });
        return;
      }
      next();
    },
    touchMove(e) {
      if (this.isPC && this.enableHover && !this.isDown) {
        this.isDown = true;
      }
      const touches = convertTouchesToArray(e.touches);
      if (this.chart && this.isDown) {
        const handler = this.chart.getZr().handler;
        dispatch.call(handler, "mousemove", this.getTouch(e, touches));
        handler.processGesture(wrapTouch(e), "change");
      }
    },
    touchEnd(e) {
      this.isDown = false;
      if (this.chart) {
        const touches = convertTouchesToArray(e.changedTouches);
        const { x } = (touches && touches[0]) || {};
        const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
        if (this.landscape) {
          [touch.x, touch.y] = [touch.y, this.height - touch.x];
        }
        const handler = this.chart.getZr().handler;
        const isClick =
          Math.abs(touch.x - this.startX) < 10 &&
          new Date() - this.startT < 200;
        dispatch.call(handler, "mouseup", touch);
        handler.processGesture(wrapTouch(e), "end");
        if (isClick) {
          dispatch.call(handler, "click", touch);
        } else {
          this.endTimer = setTimeout(() => {
            dispatch.call(handler, "mousemove", { x: 999999999, y: 999999999 });
            dispatch.call(handler, "mouseup", { x: 999999999, y: 999999999 });
          }, 50);
        }
      }
    },
    // #endif
    // #ifdef H5
    mousewheel(e) {
      if (this.chart) {
        dispatch.call(
          this.chart.getZr().handler,
          "mousewheel",
          this.getTouch(e),
        );
      }
    },
    // #endif
  },
};
</script>
<style>
.lime-echart {
  position: relative;
  /* #ifndef APP-NVUE */
  width: 100%;
  height: 100%;
  /* #endif */
  /* #ifdef APP-NVUE */
  flex: 1;
  /* #endif */
}
.lime-echart__canvas {
  /* #ifndef APP-NVUE */
  width: 100%;
  height: 100%;
  /* #endif */
  /* #ifdef APP-NVUE */
  flex: 1;
  /* #endif */
}
/* #ifndef APP-NVUE */
.lime-echart__mask {
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  z-index: 1;
}
/* #endif */
</style>
